#include "Client.hpp"

Client::Client() {
    sf::err().rdbuf(cout.rdbuf());
}

int Client::setup() {
   
    window = new sf::RenderWindow(sf::VideoMode(800, 640), "nhns");
    camera = new Camera(0, 800, 640);

    GameDrawer::Instance()->setWindow(window);
    if (!GameDrawer::Instance()->loadTextures()) {
        return 1;
    }

    return 0;
}

/** 
* Connect to a server and begin game.
*/
int Client::connect(std::string sAddr, unsigned short sPort) {
    clientTime.restart();
    networkHandler = new NetworkHandler(sAddr, sPort);
	networkHandler->addMessage(Message::m_c_ConnectionRequest, 0);
    networkHandler->forceSendMessages();

    cout << "Send connection request" << endl;
    //Wait for connection request accept
    MessageQueue* serverResponse = 0;
    serverResponse = networkHandler->getServerMessagesWait(3000);

    if (serverResponse == 0 || (serverResponse != 0 && *serverResponse->getFirstMessage(Message::m_s_ConnectionAccept) == 0)) {
        cout << "Connection request denied or timed out." << endl;
        return 2;
    } else {
        cout << "Connection request accepted" << endl;
        serverResponse = 0;
    }

    //Sync client time with server.
    cout << "Clock sync" << endl;
    int syncCounter = 0;
    uint32_t previousOffset = 0;
    MessageIterator response;
    while(true){
        //Send clock sync request.
		networkHandler->addMessage(Message::m_c_ClockSyncReq, 0);
        uint32_t requestTime = networkHandler->serverTime.getTime();
        cout << "Request Time: " << requestTime << endl;
        networkHandler->forceSendMessages();

        //Get request response.
        serverResponse = networkHandler->getServerMessagesWait(3000);
        if(!serverResponse){ //Request timed out after 3 seconds.
            return 3;
        }

        response = serverResponse->getFirstMessage(Message::m_s_ClockSyncResp);
        if((*response)){

            uint32_t serverTime = *(uint32_t*)(*response)->buffer;
            cout << "Server Time: " << serverTime << endl;
            uint32_t responseTime = networkHandler->serverTime.getTime();
            cout << "Response Time: " << responseTime << endl;
            uint32_t offset = ((serverTime - requestTime) + (serverTime - responseTime))/2;
            cout << "Offset: " << offset << endl << endl;

            if(abs((long int)previousOffset) > abs((long int)offset) || syncCounter == 0){
                previousOffset = offset;
                networkHandler->setServerTime(networkHandler->serverTime.getTime() + offset);
            }
            if(offset == 0 || syncCounter >= 5)
                break;
            syncCounter++;
        }
    }

    //Notify server that we are ready to join game.
	networkHandler->addMessage(Message::m_c_LoadingComplete, 0);
    networkHandler->forceSendMessages();
    serverResponse = networkHandler->getServerMessagesWait(3000);

    //If server accepted client then parse the game state and begin engine.
    response = serverResponse->getFirstMessage(Message::m_s_ClientAdd);
    if ((*response)){
        clientIndex = *(byte*) (*response)->buffer;
        networkHandler->setClientIndex(clientIndex);
        if(*(++response) != 0 && (*response)->code == Message::m_s_GameState){
            Packet_GameState* state = (Packet_GameState*)(*response)->buffer;
            engine = new ClientEngine(window, networkHandler, "New.dat", camera, clientIndex, state);
            ++response;
            while((*response)){
				if((*response)->code == Message::m_s_NewClient){
					Packet_ClientState* client = (Packet_ClientState*)(*response)->buffer;
					engine->entityManager.players[client->client] = new Player(engine->map, 
					std::string(client->name), GameDrawer::Instance()->spriteManager.getSpriteSet("c1"), 
					client->x, client->y, client->direction);
        
					if(clientIndex == client->client){
						engine->entityManager.player = new Player(engine->map, std::string(client->name), GameDrawer::Instance()->spriteManager.getSpriteSet("c1"),
								client->x, client->y, client->direction);
						engine->entityManager.player->collisionBoxColor = sf::Color::Red;
						engine->camera->setTarget(engine->entityManager.player);
					}
				}else if((*response)->code == Message::m_s_NewEntity){
					Packet_EntityState* es = (Packet_EntityState*) (*response)->buffer;
					Character* character = (Character*)engine->entityManager.createEntity(es->script, es->index);
					character->setLocation(es->x, es->y, es->direction);
				}
                ++response;
            }
            cout << "Game State: " << (int)engine->playerCount << "/" << (int)engine->entityManager.maxPlayers << endl;
        }else{
            return 5;
        }
    } else {
        return 4;
    }
    //Connected successfully.
    return 0;
}

/** 
* Main game loop.
*/
int Client::run() {

    sf::Clock updateTimer;
	sf::Int64 updateAccumulator = 0;
    while (window->isOpen() && engine->run) {

        updateAccumulator += updateTimer.getElapsedTime().asMicroseconds();
        updateTimer.restart();
        
        MessageQueue* serverMessages = networkHandler->getServerMessages();
        if (serverMessages && serverMessages->size > 0) {
            engine->handleMessages(serverMessages, networkHandler->serverTime.getTime());
        }
		
        //Semi fixed time step.
        //We update game state every 20ms.
        while(updateAccumulator >= 20000){
            engine->update();
            updateAccumulator -= 20000;
        }

        // Clear screen
        window->clear();

		//Set debug strings before drawing.
		engine->fpsCounter.increment(networkHandler->serverTime.getTime());
		string fps = "FPS: " + StringHelper::itos(engine->fpsCounter.getCounter());
		engine->fpsLabel.setString(fps);

		string mips = "MIPS: " + StringHelper::itos(engine->mipsCounter.getCounter());
		engine->mipsLabel.setString(mips);

		string mops = "MOPS: " + StringHelper::itos(engine->mopsCounter.getCounter());
		engine->mopsLabel.setString(mops);

        window->draw(*engine);
        GameDrawer::Instance()->spriteManager.update(networkHandler->serverTime.getTime());

        window->display();

        sf::sleep(sf::milliseconds(1));
    }
    //Send disconnect message to server.
    engine->disconnect();

    cleanup();
    return 0;
}

void Client::cleanup() {
    delete engine;
    engine = 0;

    networkHandler->closeConnection();
    delete networkHandler;
    networkHandler = 0;

    delete camera;
    camera = 0;

    delete GameDrawer::Instance();

    delete window;
    window = 0;
}